﻿// ********************************************************************************************************
// <copyright file="BOShapeData.cs" company="TopX Geo-ICT">
//     Copyright (c) 2012 TopX Geo-ICT. All rights reserved.
// </copyright>
// ********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http:// www.mozilla.org/MPL/ 
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
// 
// The Initial Developer of this version is Jeen de Vegt.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
// Change Log: 
// Date           Changed By      Notes
// 29 March 2012  Jeen de Vegt    Inital coding
// ********************************************************************************************************
namespace TableEditorNew.BO
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Windows.Forms;
    using MapWinGIS;

    /// <summary>
    ///  Class for handling the shapedata 
    /// </summary>
    public class BOShapeData : MapWinGIS.ICallback
    {
        /// <summary>The shapefile-object</summary>
        private Shapefile sf;

        /// <summary>Initializes a new instance of the BOShapeData class</summary>
        /// <param name = "shapeFile">The shapeFile.</param>
        public BOShapeData(Shapefile shapeFile)
        {
            this.sf = shapeFile;
        }

        /// <summary>Check to see if dat has changed</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        /// <returns>The result</returns>
        public bool DataChanged(DataTable dataTable) 
        {
            bool dataChanged = false;

            if ((dataTable.Columns.Count - 1) != this.sf.NumFields)
            {
                dataChanged = true;
            }

            for (int i = 1; i <= this.sf.NumFields; i++)
            {   
                if (dataTable.Columns[i].ColumnName != this.sf.get_Field(i - 1).Name)
                {
                    dataChanged = true;
                }

                if ((bool)dataTable.Columns[i].ExtendedProperties["removed"])
                {
                    dataChanged = true;
                }
            }

            if (dataTable.GetChanges() != null)
            {
                dataChanged = true;
            }

            return dataChanged;
        }

        /// <summary>Get the shapefile data as a datatable</summary>
        /// <returns>The datatable with shapefiledata</returns>
        public DataTable GetDataTable()
        {
            DataTable dt = this.CreateTable();

            int numShapes = this.sf.NumShapes;
            int numfields = this.sf.NumFields;

            for (int j = 0; j < numShapes; j++)
            {
                DataRow dr = dt.NewRow();

                for (int i = 0; i < numfields; i++)
                {
                    dr[i + 1] = this.sf.get_CellValue(i, j);
                }

                dt.Rows.Add(dr);

                if (j % 100 == 0)
                {
                    // TODO: Add progress bar
                    Application.DoEvents();
                }
            }

            dt.AcceptChanges();
            return dt;
        }

        /// <summary>Save data to shapefile</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        public void SaveData(DataTable dataTable)
        {
            if (!this.sf.StartEditingTable(null))
            {
                MessageBox.Show(string.Format("Error in StartEditingTable: {0}", this.sf.get_ErrorMsg(this.sf.LastErrorCode)));
            }

            this.AddField(dataTable);

            this.ChangeData(dataTable);
            
            this.ChangeFieldNames(dataTable);

            this.DeleteField(dataTable);

            if (!this.sf.StopEditingTable(true, this))
            {
                MessageBox.Show(string.Format("Error in StopEditingTable: {0}", this.sf.get_ErrorMsg(this.sf.LastErrorCode)));
            }
            else
            {
                dataTable.AcceptChanges();
            }
        }

        /// <summary>Change the fieldnames in the shapefile</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        private void ChangeFieldNames(DataTable dataTable)
        {
            for (int i = 1; i < dataTable.Columns.Count; i++)
            {
                Field field = this.sf.get_Field(i - 1);

                if (dataTable.Columns[i].ColumnName != field.Name)
                {
                    field.Name = dataTable.Columns[i].ColumnName;
                    this.sf.Table.EditReplaceField(i - 1, field, null);
                }
            }
        }

        /// <summary>Changes the data in the shapefile</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        private void ChangeData(DataTable dataTable)
        {
            DataTable datatChangedData = dataTable.GetChanges();

            if (datatChangedData != null)
            {
                for (int i = 0; i < datatChangedData.Rows.Count; i++)
                {
                    for (int j = 0; j < this.sf.NumFields; j++)
                    {
                        int shapeId = Convert.ToInt32(datatChangedData.Rows[i][0]);
                        if (!this.sf.EditCellValue(j, shapeId, datatChangedData.Rows[i][j + 1]))
                        {
                            MessageBox.Show(string.Format("Error in EditCellValue: {0}", this.sf.get_ErrorMsg(this.sf.LastErrorCode)));
                        }
                    }
                }
            }
        }

        /// <summary>Add fields to shapefile</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        private void AddField(DataTable dataTable)
        {
            for (int newCol = this.sf.NumFields + 1; newCol < dataTable.Columns.Count; newCol++)
            {
                DataColumn column = dataTable.Columns[newCol];
                this.AddField(column);
            }
        }

        /// <summary>Delete fields from shapefile</summary>
        /// <param name = "dataTable">The datatable with data.</param>
        private void DeleteField(DataTable dataTable)
        {
            for (int col = dataTable.Columns.Count-1; col > 0; col--)
            {
                if ((bool)dataTable.Columns[col].ExtendedProperties["removed"])
                {
                    this.sf.EditDeleteField(col - 1, null);
                }
            }
        }

        /// <summary>Add column to the datatable</summary>
        /// <param name = "dt">The datatable to add the column to.</param>
        /// <param name = "fieldName">The name of the new column.</param>
        /// <param name = "fieldType">The type of the new column.</param>
        /// <param name = "precision">The precision of the new column.</param>
        /// <param name = "width">The width of th new column.</param>
        public static void AddDataColumn(DataTable dt, string fieldName, string fieldType, string precision, int width)
        {
            DataColumn dataColumn = new DataColumn
                {
                    ColumnName = fieldName, 
                    DataType = GetFieldType(fieldType)
                };
            dataColumn.ExtendedProperties.Add("removed", false);

            if (dataColumn.DataType == typeof(double))
            {
                dataColumn.ExtendedProperties.Add("precision", precision);
            }

            if (dataColumn.DataType == typeof(string))
            {
                dataColumn.MaxLength = width;
            }
            else
            {
                dataColumn.ExtendedProperties.Add("width", width);
            }

            dt.Columns.Add(dataColumn);
        }

        /// <summary>Get a list of fieldnames that are visible</summary>
        /// <param name = "dt">The datatable.</param>
        /// <returns>List of fieldnames</returns>
        public static string[] GetVisibleFieldNames(DataTable dt)
        {
            List<string> names = new List<string>();

            for (int i = 1; i < dt.Columns.Count; i++)
            {
                DataColumn dc = dt.Columns[i];

                if (!(bool)dt.Columns[i].ExtendedProperties["removed"])
                {
                    names.Add(dt.Columns[i].ColumnName);
                }
            }

            return names.ToArray();
        }

        /// <summary>Add a field to the shapefile</summary>
        /// <param name = "column">The column to add.</param>
        public void AddField(DataColumn column)
        {
            string name = column.ColumnName;
            FieldType fieldType = this.GetMapWindowFieldType(column.DataType);
            int precision = this.GetPrecision(column);
            int width = this.GetWidth(column);
            Field f = new Field();
            f.Name = name;
            f.Precision = precision;
            f.Width = width;
            bool b;
            int fieldIndexNew = 0;
            b = this.sf.EditInsertField(f,ref fieldIndexNew,null);
        }

        /// <summary>Checks if a given fieldname is valid</summary>
        /// <param name = "fieldName">The given fieldname.</param>
        /// <param name = "dt">The datatable.</param>
        /// <param name = "message">A message which can be returned to the calling function.</param>
        /// <returns>The result</returns>
        public static bool IsNameValid(string fieldName, DataTable dt, ref string message)
        {
            bool isValid = true;

            if (fieldName == string.Empty)
            {
                message = "Please enter a name.";
                isValid = false;
            }

            if (fieldName.Length > 10)
            {
                message = "Max fieldlength is 10.";
                isValid = false;
            }

            foreach (DataColumn dataColumn in dt.Columns)
            {
                if (dataColumn.ColumnName == fieldName)
                {
                    message = "Fieldname already exists.";
                    isValid = false;
                }
            }

            return isValid;
        }

        /// <summary>Get the width of a column</summary>
        /// <param name = "column">The column.</param>
        /// <returns>The width</returns>
        private int GetWidth(DataColumn column)
        {
            int width;
            if (column.DataType == typeof(string))
            {
                width = column.MaxLength;
            }
            else
            {
                width = (int)column.ExtendedProperties["width"];
            }

            return width;
        }

        /// <summary>Get the precision of a column</summary>
        /// <param name = "column">The column.</param>
        /// <returns>The precision</returns>
        private int GetPrecision(DataColumn column)
        {
            int precision = 0;

            if (column.DataType == typeof(double))
            {
                precision = Convert.ToInt32(column.ExtendedProperties["precision"]);
            }

            return precision;
        }

        /// <summary>Create a datatable based on the shapefile</summary>
        /// <returns>The datatable</returns>
        private DataTable CreateTable()
        {
            DataTable dataTable = new DataTable();
            DataColumn dataColumn;

            dataColumn = new DataColumn();
            dataColumn.DataType = typeof(int);
            dataColumn.ColumnName = "SHAPE__ID";
            dataColumn.AutoIncrement = true;
            dataColumn.Unique = true;

            dataTable.Columns.Add(dataColumn);

            for (int i = 0; i < this.sf.NumFields; i++)
            {
                dataColumn = new DataColumn();

                Field field = this.sf.get_Field(i);

                dataColumn.ColumnName = this.GetValidFieldName(field.Name, dataTable);

                dataColumn.DataType = GetFieldType(field.Type);
                dataColumn.ExtendedProperties.Add("removed", false);
                dataTable.Columns.Add(dataColumn);
            }

            return dataTable;
        }

        /// <summary>Get a valid fieldname</summary>
        /// <param name = "fieldName">The fieldName.</param>
        /// <param name = "dataTable">The dataTable.</param>
        /// <returns>The valid fieldName</returns>
        private string GetValidFieldName(string fieldName, DataTable dataTable)
        {
            string displayName = string.Empty;

            string errorMessage = string.Empty;
            bool isValid = IsNameValid(fieldName, dataTable, ref errorMessage);
            if (!isValid)
            {
                displayName = this.GenerateNewFieldName(fieldName, dataTable);
            }
            else
            {
                displayName = fieldName;
            }

            return displayName;
        }

        /// <summary>Generate a valid fieldname</summary>
        /// <param name = "fieldName">The fieldName.</param>
        /// <param name = "dataTable">The dataTable.</param>
        /// <returns>The generated fieldName</returns>
        private string GenerateNewFieldName(string fieldName, DataTable dataTable)
        {
            bool isValid = false;
            string newName = string.Empty;

            if (fieldName.Length > 9)
            {
                fieldName = fieldName.Substring(0, 9);
            }

            for (int number = 1; number < 10; number++)
            {
                newName = string.Format("{0}{1}", fieldName, number);

                string errorMessage = string.Empty;
                if (IsNameValid(newName, dataTable, ref errorMessage))
                {
                    isValid = true;
                    break;
                }
            }

            if (!isValid)
            {
                newName = Guid.NewGuid().ToString().Substring(0, 10);
                MessageBox.Show(string.Format("Field {0} will shown as {1}", fieldName, newName));
            }
            
            return newName;
        }

        /// <summary>Get the fieldtype</summary>
        /// <param name = "fieldType">The fieldType as string.</param>
        /// <returns>The fieldType as Type</returns>
        private static Type GetFieldType(string fieldType)
        {
            Type type = null;

            switch (fieldType)
            {
                case "Double":
                    type = typeof(double);
                    break;
                case "Integer":
                    type = typeof(int);
                    break;
                case "String":
                    type = typeof(string);
                    break;
                default:
                    // Default to string
                    type = typeof(string);
                    break;
            }

            return type;
        }

        /// <summary>Get the fieldtype</summary>
        /// <param name = "fieldType">The fieldType as fieldType.</param>
        /// <returns>The fieldType as Type</returns>
        private Type GetFieldType(FieldType fieldType)
        {
            Type type = null;

            switch (fieldType)
            {
                case FieldType.DOUBLE_FIELD:
                    type = typeof(double);
                    break;
                case FieldType.INTEGER_FIELD:
                    type = typeof(int);
                    break;
                case FieldType.STRING_FIELD:
                    type = typeof(string);
                    break;
                default:
                    // Default to string
                    type = typeof(string);
                    break;
            }

            return type;
        }

        /// <summary>Get the MapwIndow-Type</summary>
        /// <param name = "fieldType">The fieldType as Type.</param>
        /// <returns>The fieldType as MapwIndow-Type</returns>
        private FieldType GetMapWindowFieldType(Type fieldType)
        {
            // Default string
            FieldType type = FieldType.STRING_FIELD;

            if (fieldType == typeof(double))
            {
                type = FieldType.DOUBLE_FIELD;
            }
            else if (fieldType == typeof(int))
            {
                type = FieldType.INTEGER_FIELD;
            }

            return type;
        }

        #region ICallback Members
        /// <summary>Show Error</summary>
        /// <param name = "KeyOfSender">The key of sender.</param>
        /// <param name = "ErrorMsg">The errorMessage.</param>
        public void Error(string KeyOfSender, string ErrorMsg)
        {
            MessageBox.Show("Error: " + ErrorMsg);
        }

        /// <summary>Show progress</summary>
        /// <param name = "KeyOfSender">The key of sender.</param>
        /// <param name = "Percent">The percentage.</param>
        /// <param name = "Message">The message.</param>
        public void Progress(string KeyOfSender, int Percent, string Message)
        {
            if (string.IsNullOrEmpty(Message))
            {
                MapWinUtility.Logger.Progress(Percent, 100);
            }
            else
            {
                MapWinUtility.Logger.Progress(Message, Percent, 100);
            }
        }

        #endregion
    }
}
